﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Markup;

namespace Sakura.Silverlight.Extensions
{
	/// <summary>
	/// 表示一个对内容实施强制按比例缩放的容器。
	/// </summary>
	public class RatioBox : Panel
	{
		#region 公共属性

		/// <summary>
		/// 获取或设置要强制缩放比例的子元素。
		/// </summary>
		public UIElement Child
		{
			get;
			set;
		}

		/// <summary>
		/// 获取或设置内容需要实施的纵横比，此值为宽度除以高度。如果设置为 <see cref="System.Double.NaN"/> 则表示不限制纵横比。
		/// </summary>
		public double Ratio
		{
			get
			{
				return (double)GetValue(RatioProperty);
			}
			set
			{
				SetValue(RatioProperty, value);
			}
		}



		/// <summary>
		/// 获取或设置当水平方向尺寸必须收缩以适应比例要求时，水平方向的对齐方式。
		/// </summary>
		public HorizontalAlignment HorizontalContentFlowAlignment
		{
			get
			{
				return (HorizontalAlignment)GetValue(HorizontalContentFlowAlignmentProperty);
			}
			set
			{
				SetValue(HorizontalContentFlowAlignmentProperty, value);
			}
		}


		/// <summary>
		/// 获取或设置当垂直方向尺寸必须收缩以适应比例要求时，垂直方向的对齐方式。
		/// </summary>
		public VerticalAlignment VerticalContentFlowAlignment
		{
			get
			{
				return (VerticalAlignment)GetValue(VerticalContentFlowAlignmentProperty);
			}
			set
			{
				SetValue(VerticalContentFlowAlignmentProperty, value);
			}
		}

		#endregion

		#region 依赖项属性定义

		/// <summary>
		/// 标识 <see cref="Ratio"/> 依赖项属性。
		/// </summary>
		public static readonly DependencyProperty RatioProperty = DependencyProperty.Register("Ratio", typeof(double), typeof(FrameworkElement), new PropertyMetadata(double.NaN, OnRatioPropertyChanged));


		/// <summary>
		/// 标识 <see cref="HorizontalContentFlowAlignment"/> 依赖项属性。
		/// </summary>
		public static readonly DependencyProperty HorizontalContentFlowAlignmentProperty = DependencyProperty.Register("HorizontalContentFlowAlignment", typeof(HorizontalAlignment), typeof(RatioBox), new PropertyMetadata(HorizontalAlignment.Center, OnHorizontalContentFlowAlignmentPropertyChanged));


		/// <summary>
		/// 标识 <see cref="VerticalContentFlowAlignment"/> 依赖项属性。
		/// </summary>
		public static readonly DependencyProperty VerticalContentFlowAlignmentProperty = DependencyProperty.Register("VerticalContentFlowAlignment", typeof(VerticalAlignment), typeof(RatioBox), new PropertyMetadata(VerticalAlignment.Center, OnVerticalContentFlowAlignmentPropertyChanged));

		/// <summary>
		/// 当 <see cref="Ratio"/> 属性发生更改时进行的验证回调。
		/// </summary>
		/// <param name="d">相关的依赖对象。</param>
		/// <param name="e">事件包含的参数。</param>
		private static void OnRatioPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			var value = (double)e.NewValue;

			if (!double.IsNaN(value) && value < 0)
			{
				throw new ArgumentNullException("value", "长宽比不能为负数。");
			}

			var obj = (RatioBox)d;
			obj.InvalidateMeasure();
		}

		/// <summary>
		/// 当 <see cref="HorizontalContentFlowAlignment"/> 属性发生更改时进行的验证回调。
		/// </summary>
		/// <param name="d">相关的依赖对象。</param>
		/// <param name="e">事件包含的参数。</param>
		private static void OnHorizontalContentFlowAlignmentPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			var value = (HorizontalAlignment)e.NewValue;

			switch (value)
			{
				case HorizontalAlignment.Left:
				case HorizontalAlignment.Right:
				case HorizontalAlignment.Center:
					break;
				case HorizontalAlignment.Stretch:
					throw new ArgumentException("HorizontalContentFlowAlignment 属性的值不允许使用 Stretch。");
				default:
					throw new ArgumentException("HorizontalContentFlowAlignment 属性的值不是有效的 HorizontalAlignment 枚举项之一。");
			}
			var obj = (RatioBox)d;
			obj.InvalidateArrange();
		}

		/// <summary>
		/// 当 <see cref="VerticalContentFlowAlignment"/> 属性发生更改时进行的验证回调。
		/// </summary>
		/// <param name="d">相关的依赖对象。</param>
		/// <param name="e">事件包含的参数。</param>
		private static void OnVerticalContentFlowAlignmentPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			var value = (VerticalAlignment)e.NewValue;

			switch (value)
			{
				case VerticalAlignment.Top:
				case VerticalAlignment.Bottom:
				case VerticalAlignment.Center:
					break;
				case VerticalAlignment.Stretch:
					throw new ArgumentException("VerticalContentFlowAlignment 属性的值不允许使用 Stretch。");
				default:
					throw new ArgumentException("VerticalContentFlowAlignment 属性的值不是有效的 VerticalAlignment 枚举项之一。");
			}

			var obj = (RatioBox)d;
			obj.InvalidateArrange();
		}

		#endregion

		#region 辅助布局方法

		/// <summary>
		/// 根据缩放比例，对尺寸进行裁剪以获得最终尺寸。
		/// </summary>
		/// <param name="maxSize">允许的最大尺寸。</param>
		/// <returns>裁剪后的最终尺寸。</returns>
		private Size GetStretchedSize(Size maxSize)
		{
			if (double.IsNaN(this.Ratio))
			{
				return maxSize;
			}

			var realRatio = maxSize.Width / maxSize.Height;

			var finalWidth = maxSize.Width;
			var finalHeight = maxSize.Height;

			if (this.Ratio > realRatio)
			{
				finalHeight = maxSize.Width / this.Ratio;
			}
			else
			{
				finalWidth = maxSize.Height * this.Ratio;
			}

			return new Size(finalWidth, finalHeight);
		}

		/// <summary>
		/// 根据内容的实际尺寸和对齐方式，决定放置位置。
		/// </summary>
		/// <param name="parentSize">父容器的尺寸。</param>
		/// <param name="realSize">内容的实际尺寸。</param>
		/// <returns>内容的实际放置位置/</returns>
		private Point GetContentLocation(Size parentSize, Size realSize)
		{
			double x;
			double y;

			switch (this.HorizontalContentFlowAlignment)
			{
				case System.Windows.HorizontalAlignment.Left:
					x = 0.0;
					break;
				case System.Windows.HorizontalAlignment.Right:
					x = parentSize.Width - realSize.Width;
					break;
				case System.Windows.HorizontalAlignment.Center:
					x = (parentSize.Width - realSize.Width) / 2;
					break;
				// 不会发生
				default:
					throw new InvalidOperationException();
			}

			switch (this.VerticalContentFlowAlignment)
			{
				case System.Windows.VerticalAlignment.Top:
					y = 0.0;
					break;
				case System.Windows.VerticalAlignment.Bottom:
					y = parentSize.Height - realSize.Height;
					break;
				case System.Windows.VerticalAlignment.Center:
					y = (parentSize.Height - realSize.Height) / 2;
					break;
				// 不会发生
				default:
					throw new InvalidOperationException();
			}

			return new Point(x, y);
		}

		/// <summary>
		/// 测量子元素在布局中所需的大小，然后确定派生类的大小。
		/// </summary>
		/// <param name="availableSize">此元素可以赋给子元素的可用大小。</param>
		/// <returns>此元素在布局过程中所需的大小，这是由此元素根据对其子元素大小的计算而确定的。</returns>
		protected override Size MeasureOverride(Size availableSize)
		{
			var contentSize = this.GetStretchedSize(availableSize);

			if (this.Child != null)
			{
				this.Child.Measure(contentSize);
			}

			foreach (var i in this.Children)
			{
				i.Measure(contentSize);
			}

			return base.MeasureOverride(availableSize);
		}

		/// <summary>
		/// 定位子元素并确定派生类的大小。
		/// </summary>
		/// <param name="finalSize">父级中此元素应用来排列自身及其子元素的最终区域。</param>
		/// <returns>所用的实际大小。</returns>
		protected override Size ArrangeOverride(Size finalSize)
		{
			var childSize = this.GetStretchedSize(finalSize);
			var location = this.GetContentLocation(finalSize, childSize);

			if (this.Child != null)
			{
				this.Child.Arrange(new Rect(location, childSize));
			}

			foreach (var i in this.Children)
			{
				i.Arrange(new Rect(location, childSize));
			}

			return base.ArrangeOverride(finalSize);
		}

		#endregion
	}
}
